package com.asset.dispoal.file.evidenceRecognition.service.commom;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.asset.dispoal.business.bsCase.domain.db.entity.BsCaseDisposalFile;
import com.asset.dispoal.business.bsCase.domain.db.mapper.BsCaseMapper;
import com.asset.dispoal.business.bsEvidenceRecognition.domain.redis.dto.RedisMatchDTO;
import com.asset.dispoal.file.evidenceRecognition.domain.dto.EvidenceFileDTO;
import com.asset.dispoal.file.evidenceRecognition.dto.CaseEvidenceRecognitionRequest;
import com.asset.dispoal.file.evidenceRecognition.rar.FileDecUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import tech.mhuang.pacebox.core.date.DateTimeUtil;
import tech.mhuang.pacebox.core.ddd.BaseCommand;
import tech.mhuang.pacebox.core.exception.BusinessException;
import tech.mhuang.pacebox.core.file.FileUtil;
import tech.mhuang.pacebox.core.id.BaseIdeable;
import tech.mhuang.pacebox.core.io.IOUtil;
import tech.mhuang.pacebox.springboot.core.entity.RequestModel;
import tech.mhuang.pacebox.springboot.core.local.GlobalHeaderThreadLocal;
import tech.mhuang.pacebox.springboot.core.rest.SingleRestTemplate;
import tech.mhuang.pacebox.springboot.protocol.Result;
import tech.mhuang.pacebox.springboot.redis.commands.IRedisExtCommands;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.net.InetAddress;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;


@Component
@Slf4j
public class CaseEvidenceUploadCommand implements BaseCommand<CaseEvidenceRecognitionRequest, Result> {

    @Autowired
    private Environment environment;

    @Autowired
    BsCaseMapper caseMapper;

    @Autowired
    private BaseIdeable<String> snowFlake;

    @Autowired
    private RedisProperties redisProperties;

    @Autowired
    private SingleRestTemplate singleRestTemplate;

    @Autowired
    private IRedisExtCommands redisExtCommands;

    @SneakyThrows
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Result execute(CaseEvidenceRecognitionRequest request) {
        String genId = snowFlake.generateId();
        String userId = GlobalHeaderThreadLocal.getOrException().getUserId();
        String uploadDir = environment.getProperty("file.evidence.url");
        LocalDateTime now = LocalDateTime.now();
//        String baseDir = Paths.get(uploadDir,
//                "casesource",
//                String.valueOf(now.getYear()),
//                String.valueOf(now.getMonthValue()),
//                String.valueOf(now.getDayOfMonth()),
//                request.getCurrentState(),
//                request.getFileType()).toString();
        String baseDir = uploadDir +
                "/casesource/" +
                now.getYear() +
                File.separator +
                now.getMonthValue() +
                File.separator +
                now.getDayOfMonth() +
                File.separator +
                request.getCurrentState() +
                File.separator +
                request.getFileType() +
                File.separator;
        String resolveUrl = environment.getProperty("file.caseRegister.url") +
                File.separator +
                now.getYear() +
                File.separator +
                now.getMonthValue() +
                File.separator +
                now.getDayOfMonth() +
                File.separator +
                request.getCurrentState() +
                File.separator +
                request.getFileType() +
                File.separator;
        String baseResolveUrl = uploadDir + resolveUrl;
        File baseDirFile = new File(baseDir);
        if (!baseDirFile.exists()) {
            baseDirFile.mkdirs();
        }
        // 分片文件存放包路径
        String targetDir = baseDir + request.getFilename().split("\\.")[0] + request.getUid();
        // 分片文件包路径+文件名+文件类型
        String targetFile = baseDir + request.getUid() + request.getFilename();
        // 分片文件对象
        File chunkFile;
        boolean isSuccess = false;
        if (request.getTotalChunk() > 0) {
            File dir = new File(targetDir);
            // 文件包路径创建
            if (!dir.exists()) {
                dir.mkdir();
            }
            String chunkFileName = "&&&&&" + request.getChunk();
            chunkFile = new File(targetDir, chunkFileName);
            if (!chunkFile.getParentFile().exists()) {
                chunkFile.getParentFile().mkdirs();
            }
            request.getFile().transferTo(chunkFile);
            if (mergeFile(targetDir, targetFile, request.getTotalChunk())) {
                isSuccess = true;
            }
        } else {
            chunkFile = new File(targetFile);
            if (!chunkFile.getParentFile().exists()) {
                chunkFile.getParentFile().mkdirs();
            }
            request.getFile().transferTo(chunkFile);
            isSuccess = true;
        }
        if (isSuccess) {
            //需要处理的文件集合
            List<EvidenceFileDTO> proccessFileList = new ArrayList<>();
//            if (request.getFilename().matches(".*.zip")) {
//                log.info(request.getFilename() + "案件证据----是压缩文件夹-------");
//                unZipFile(baseDir, baseResolveUrl, request.getUid() + request.getFilename(), request, resolveUrl, userId, now, proccessFileList);
//            } else if (request.getFilename().matches(".*.rar")) {
//                throw new BusinessException(500, "暂不支持rar格式传输");
//
//            }
            if (request.getFilename().matches(".*.zip") || request.getFilename().matches(".*.rar")) {
                unZipAndRarFile(baseDir, baseResolveUrl, request.getUid() + request.getFilename(), request, resolveUrl, userId, now, proccessFileList);
            } else {
                BsCaseDisposalFile caseDisposalFile = new BsCaseDisposalFile();
                caseDisposalFile.setId(snowFlake.generateId());
                caseDisposalFile.setCurrentState(request.getCurrentState());
                caseDisposalFile.setFileType(request.getFileType());
                caseDisposalFile.setCreateUser(userId);
                caseDisposalFile.setCreateTime(DateTimeUtil.localDateTimeToDate(now));
                caseDisposalFile.setOperateUser(userId);
                caseDisposalFile.setOperateTime(DateTimeUtil.localDateTimeToDate(now));
                caseDisposalFile.setOriginalName(request.getFilename());
                caseDisposalFile.setReplaceName(chunkFile.getName());
                caseDisposalFile.setFilePath(resolveUrl + chunkFile.getName());
                caseDisposalFile.setUploadWay("1");
                caseMapper.insertDisposeFile(caseDisposalFile);
                EvidenceFileDTO evidenceFileDTO = new EvidenceFileDTO();
                evidenceFileDTO.setId(caseDisposalFile.getId());
                evidenceFileDTO.setPath(chunkFile.getParent());
                evidenceFileDTO.setUrl(chunkFile.getName());
                proccessFileList.add(evidenceFileDTO);
            }
            if ("1".equals(request.getType())) {
                //识别组装数据，包含识别数据、匹配的关键字、识别相关的redis参数等
                List<Map<String, Object>> dataList = caseMapper.queryParamByParam(request.getActionKey());
                redisExtCommands.hset(6, "ocr_case_data", genId, JSON.toJSONString(dataList, JSONWriter.Feature.WriteMapNullValue));
                Map<String, List<EvidenceFileDTO>> processFileMap = proccessFileList.stream().collect(Collectors.groupingBy(EvidenceFileDTO::getPath));
                Set<String> pathSet = processFileMap.keySet();
                JSONArray resultArr = new JSONArray(pathSet.size());
                for (String path : pathSet) {
                    JSONObject headerMap = new JSONObject();
                    headerMap.put("actionWay", request.getActionWay());
                    headerMap.put("actionKey", request.getActionKey());
                    headerMap.put("evidenceItemId", "1");
                    headerMap.put("cognateOthers", request.getCognateOthers());
                    headerMap.put("cognateOthersType", "1");
                    headerMap.put("cognateOthersId", "1");
                    headerMap.put("mobileName", request.getMobileName());
                    headerMap.put("x", request.getX());
                    headerMap.put("y", request.getY());
                    JSONArray allList = new JSONArray();
                    JSONArray mapList = new JSONArray();
                    JSONObject dirMap = new JSONObject();
                    processFileMap.get(path).forEach(item -> {
                        Map<String, String> sunMap = new HashMap<>(2);
                        sunMap.put("id", item.getId());
                        sunMap.put("filepath", item.getUrl());
                        mapList.add(sunMap);
                    });
                    dirMap.put("files", mapList);
                    dirMap.put("dirPath", path);
                    allList.add(dirMap);
                    headerMap.put("cognateFiles", allList);
                    resultArr.add(headerMap);
                }
                redisExtCommands.hset(6, "ocr_case_before", genId, resultArr);
                RedisMatchDTO dto = new RedisMatchDTO();
                dto.setDb("6");
                dto.setId("1");
                dto.setKey(genId);
                dto.setHost(redisProperties.getHost());
                dto.setPort(redisProperties.getPort());
                dto.setPasswd(redisProperties.getPassword());
                dto.setOcrFile("ocr_case_before");
                dto.setOcrData("ocr_case_data");
                dto.setErrorKey("err_case_key");
                dto.setNotifyKey("notify_case_key");
                try {
                    dto.setServerUrl("http://" + InetAddress.getLocalHost().getHostAddress() + ":8014" + "/business/bsEvidenceRecognition/PythonCaseCallback");
                } catch (Exception e) {
                    throw new BusinessException(Result.SYS_FAILD, "找不到请求地址", e);
                }
                RequestModel<String> requestModel = new RequestModel<String>() {
                };
                requestModel.setUrl(environment.getProperty("evidence_1_url"));
                requestModel.setMethod(HttpMethod.POST);
                requestModel.setSufUrl("/file/documentRecognition");
                requestModel.setParams(JSON.toJSON(dto));
                singleRestTemplate.request(requestModel);
            } else {
                //TODO 读取处理
            }
        }
        return Result.ok();
    }

    /**
     * 解压rar压缩包
     *
     * @param baseDir
     * @param baseResolveUrl
     * @param fileName
     * @param request
     * @param outIgnoreHeadPath
     * @param userId
     * @param now
     * @param proccessFileList
     */
    private void unZipAndRarFile(String baseDir, String baseResolveUrl, String fileName, CaseEvidenceRecognitionRequest request, String outIgnoreHeadPath, String userId, LocalDateTime now, List<EvidenceFileDTO> proccessFileList) {
        String sourceFileName = Paths.get(baseDir, fileName).toString();
        log.info("baseResolveUrl:{}   {}", baseResolveUrl, outIgnoreHeadPath);
        String generateId = snowFlake.generateId();
        //得到所有解压文件信息
        List<BsCaseDisposalFile> fileList = FileDecUtils.unRar(sourceFileName, baseResolveUrl, outIgnoreHeadPath, generateId);

        for (BsCaseDisposalFile caseDisposalFile : fileList) {
            caseDisposalFile.setCurrentState(request.getCurrentState());
            caseDisposalFile.setFileType(request.getFileType());
            caseDisposalFile.setCreateUser(userId);
            caseDisposalFile.setCreateTime(DateTimeUtil.localDateTimeToDate(now));
            caseDisposalFile.setOperateUser(userId);
            caseDisposalFile.setOperateTime(DateTimeUtil.localDateTimeToDate(now));
            //案件处置文件入库
//            caseMapper.insertDisposeFile(caseDisposalFile);
            //证据文件
            EvidenceFileDTO evidenceFileDTO = new EvidenceFileDTO();
            evidenceFileDTO.setId(caseDisposalFile.getId());
            String parentPath = Paths.get(caseDisposalFile.getFilePath()).getParent().toString();
            evidenceFileDTO.setPath(parentPath);
            evidenceFileDTO.setUrl(caseDisposalFile.getReplaceName());
            proccessFileList.add(evidenceFileDTO);
        }
        //案件处置文件入库
        caseMapper.insertDisposeFiles(fileList);
    }

    private void unZipFile(String baseDir, String outPath, String fileName, CaseEvidenceRecognitionRequest request, String outIgnoreHeadPath, String userId, LocalDateTime now, List<EvidenceFileDTO> proccessFileList) {
        String sourceFileName = baseDir + fileName;
        try (ZipFile zipFile = new ZipFile(sourceFileName, "GBK")) {
            File file;
            String generateId = snowFlake.generateId();
            Enumeration<ZipEntry> enumeration = zipFile.getEntries();
            while (enumeration.hasMoreElements()) {
                ZipEntry zipEntry = enumeration.nextElement();
                if (!zipEntry.getName().endsWith(File.separator)) {
                    String filename = zipEntry.getName();
                    boolean ismkdir = false;
                    // 检查此文件是否带有文件夹
                    if (filename.lastIndexOf("/") != -1) {
                        ismkdir = true;
                    }
                    if (zipEntry.isDirectory()) {
                        // 如果是文件夹则跳过
                        continue;
                    }
                    String orinionName = zipEntry.getName().substring(zipEntry.getName().lastIndexOf("/") + 1);
                    String suffixId = snowFlake.generateId();
                    String replaceFileName = (outPath + "/" + generateId + "_" + zipEntry.getName().replace(orinionName, suffixId + "&" + orinionName)).replace("#", "");
                    String replaceIgnoreHeadFileName = (outIgnoreHeadPath + "/" + generateId + "_" + zipEntry.getName().replace(orinionName, suffixId + "&" + orinionName)).replace("#", "");
                    file = new File(replaceFileName);
                    if (!file.exists()) {
                        // 如果是目录先创建
                        if (ismkdir) {
                            //目录先创建
                            new File(replaceFileName.substring(0, replaceFileName.lastIndexOf("/"))).mkdirs();
                        }
                        File parentDir = file.getParentFile();
                        if (!file.exists()) {
                            parentDir.mkdirs();
                        }
                    }
                    //创建解压后的文件
                    OutputStream os = new FileOutputStream(replaceFileName);
                    //带缓的写出流
                    BufferedOutputStream bos = new BufferedOutputStream(os);
                    //读取元素
                    InputStream is = zipFile.getInputStream(zipEntry);
                    //读取流的缓存流
                    BufferedInputStream bis = new BufferedInputStream(is);
                    //检查读取流，采用CRC32算法，保证文件的一致性
                    CheckedInputStream cos = new CheckedInputStream(bis, new CRC32());
                    //字节数组，每次读取1024个字节
                    byte[] b = new byte[1024];
                    // 循环读取压缩文件的值
                    while (cos.read(b) != -1) {
                        // 写入到新文件
                        bos.write(b);
                    }
                    cos.close();
                    bis.close();
                    is.close();
                    bos.close();
                    os.close();
                    log.info("正在解压文件:" + zipEntry.getName());
                    log.info(String.valueOf(zipEntry.getName().matches(".*.zip")));
                    if (zipEntry.getName().matches(".*.zip")) {
                        log.info(zipEntry.getName() + "是压缩文件-------------------");
                        unZipFile(outPath, outPath, zipEntry.getName(), request, outIgnoreHeadPath, userId, now, proccessFileList);
                    } else if (zipEntry.getName().matches(".*.rar")) {
                        throw new BusinessException(500, "暂不支持rar格式传输");
                    } else {
                        BsCaseDisposalFile caseDisposalFile = new BsCaseDisposalFile();
                        caseDisposalFile.setId(snowFlake.generateId());
                        caseDisposalFile.setCurrentState(request.getCurrentState());
                        caseDisposalFile.setFileType(request.getFileType());
                        caseDisposalFile.setCreateUser(userId);
                        caseDisposalFile.setCreateTime(DateTimeUtil.localDateTimeToDate(now));
                        caseDisposalFile.setOperateUser(userId);
                        caseDisposalFile.setOperateTime(DateTimeUtil.localDateTimeToDate(now));
                        caseDisposalFile.setOriginalName(orinionName);
                        caseDisposalFile.setReplaceName(file.getName());
                        caseDisposalFile.setFilePath(replaceIgnoreHeadFileName);
                        caseDisposalFile.setUploadWay("1");
                        caseMapper.insertDisposeFile(caseDisposalFile);
                        EvidenceFileDTO evidenceFileDTO = new EvidenceFileDTO();
                        evidenceFileDTO.setId(caseDisposalFile.getId());
                        evidenceFileDTO.setPath(file.getParent());
                        evidenceFileDTO.setUrl(file.getName());
                        proccessFileList.add(evidenceFileDTO);
                    }
                } else {
                    // 如果为空文件夹，则创建该文件夹
                    new File(outPath + zipEntry.getName()).mkdirs();
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Boolean isAllUploaded(String fileDir, int chunk) {
        File file = new File(fileDir);
        File[] files = file.listFiles();
        int fileLength = files.length;
        if (fileLength == chunk) {
            return true;
        }
        return false;
    }

    private void saveStreamToFile(SequenceInputStream inputStream, String filePath) throws Exception {
        /* 创建输出流，写入数据，合并分块 */
        OutputStream outputStream = new FileOutputStream(filePath);
        IOUtil.copy(inputStream, outputStream, true);
    }

    private boolean mergeFile(String fileDir, String fileName, Integer chunks) {
        if (isAllUploaded(fileDir, chunks)) {
            try {
                List<FileInputStream> targetList = new CopyOnWriteArrayList<>();
                File parentFile = new File(fileDir);
                File[] files = parentFile.listFiles();
                for (int i = 0; i < chunks; i++) {
                    for (File file : files) {
                        if (file.getName().contains("&&&&&" + i)) {
                            FileInputStream sourceStream = new FileInputStream(file);
                            targetList.add(sourceStream);
                            break;
                        }
                    }
                }

                Enumeration<FileInputStream> targetEnum = Collections.enumeration(targetList);
                SequenceInputStream sequenceStream = new SequenceInputStream(targetEnum);
                // 合分片文件
                saveStreamToFile(sequenceStream, fileName);

                for (File file : files) {
                    if (file.isDirectory()) {
                        FileUtil.deleteDirectory(file);
                    } else {
                        FileUtil.delete(file);
                    }
                }
                return true;
            } catch (Exception e) {
                log.error(e.getMessage());
                return false;
            }
        }
        return false;
    }
}
